package com.rave.pricechangeanimationdemo;

import android.text.TextUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * 解决项目中的运算问题和精度问题，统一输入字符串运算，运算结果返回字符串。
 * 这样设计是因为目前项目中的数值来源有用户输入和接口返回(建议用String去解析数值)，而计算的结果只用做UI显示或请求接口的参数。
 * 优点：
 * 1.有效避免new BigDecimal()传入非数值类型或空串时的异常，如果空串默认返回0；
 * 2.有效避免除法运算可能导致的异常，除0时默认返回0，除不尽时默认保留小数点后30位，也可以自定义精度；
 * 3.减少项目中String类型和Double类型以及BigDecimal之间的转换；
 * <p>
 * 不足：
 * 1.K线页面可能会出现的，频繁创建BigDecimal对象导致内存抖动的问题，K线页面目前用double做计算；尝试用对象池解决，但是BigDecimal不支持赋值，所以无法用对象池；
 * <p>
 * 作者：tianrenzheng on 2019/12/31 12:17
 * 邮箱：317642600@qq.com
 */
public class BigDecimalUtils {
    /**
     * 数字类型的默认串（使用与某些下不能默认显示0的场景）
     */
    public static final String DEFAULT_NUMBER_STRING = "--";
    /**
     * 除法运算设置一个默认精度，防止除不尽的情况发生
     */
    private static final int DIVIDE_DEFAULT_SCALE = 30;
    /**
     * 设置一个除0默认值，统一处理除0的情况
     */
    private static final String DIVIDE_ZERO_DEFAULT_VALUE = BigDecimal.ZERO.toPlainString();
    /**
     * 0值
     */
    public static final String ZERO = BigDecimal.ZERO.toPlainString();

    /**
     * 禁止外部创建对象
     */
    private BigDecimalUtils() {

    }

    /**
     * 判断字符串是不是数字
     *
     * @param string
     * @return
     */
    public static boolean isNumeric(String string) {
        String numberRex = "[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?";
        return string.matches(numberRex);
    }

    /**
     * 把字符串封装成BigDecimal，如果是非数字类型的字符串或空串则返回0
     *
     * @param str
     * @return
     */
    public static BigDecimal newBigDecimal(String str) {
        String trimStr = (str == null) ? "" : str.trim();
        if (!TextUtils.isEmpty(trimStr)) {
            try {
                return new BigDecimal(trimStr);
            } catch (Exception e) {
                e.printStackTrace();
                return BigDecimal.ZERO;
            }
        } else {
            return BigDecimal.ZERO;
        }
    }

    /**
     * 两个数值字符串相加
     *
     * @param v1 加数
     * @param v2 加数
     * @return 和
     */
    public static String add(String v1, String v2) {
        BigDecimal bigDecimal1 = newBigDecimal(v1);
        BigDecimal bigDecimal2 = newBigDecimal(v2);
        return bigDecimal1.add(bigDecimal2).toPlainString();
    }

    /**
     * 多个数值字符串相加
     *
     * @param args
     * @return
     */
    public static String add(String... args) {
        BigDecimal result = BigDecimal.ZERO;
        if(args != null) {
            for (String v : args) {
                BigDecimal temp = newBigDecimal(v);
                result = result.add(temp);
            }
        }
        return result.toPlainString();
    }

    /**
     * 两个数值字符串相加
     *
     * @param v1    加数
     * @param v2    加数
     * @param scale 小数点后精度
     * @return 和
     */
    public static String add(String v1, String v2, int scale) throws IllegalArgumentException {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }

        BigDecimal bigDecimal1 = newBigDecimal(v1);
        BigDecimal bigDecimal2 = newBigDecimal(v2);
        return bigDecimal1.add(bigDecimal2).setScale(scale, RoundingMode.DOWN).toPlainString();
    }

    /**
     * 两个数值字符串相减
     *
     * @param v1 被减数
     * @param v2 减数
     * @return 差
     */
    public static String sub(String v1, String v2) {
        BigDecimal bigDecimal1 = newBigDecimal(v1);
        BigDecimal bigDecimal2 = newBigDecimal(v2);
        return bigDecimal1.subtract(bigDecimal2).toPlainString();
    }

    /**
     * 两个数值字符串相减
     *
     * @param v1    被减数
     * @param v2    减数
     * @param scale 精度
     * @return 差
     */
    public static String sub(String v1, String v2, int scale) throws IllegalArgumentException {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }

        BigDecimal bigDecimal1 = newBigDecimal(v1);
        BigDecimal bigDecimal2 = newBigDecimal(v2);
        return bigDecimal1.subtract(bigDecimal2).setScale(scale, RoundingMode.DOWN).toPlainString();
    }

    /**
     * 两个数值字符串相乘
     *
     * @param v1 乘数
     * @param v2 乘数
     * @return 积
     */
    public static String mul(String v1, String v2) {
        BigDecimal bigDecimal1 = newBigDecimal(v1);
        BigDecimal bigDecimal2 = newBigDecimal(v2);
        return bigDecimal1.multiply(bigDecimal2).toPlainString();
    }

    /**
     * 两个数值字符串相乘
     *
     * @param v1    乘数
     * @param v2    乘数
     * @param scale 精度
     * @return 积
     */
    public static String mul(String v1, String v2, int scale) throws IllegalArgumentException {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }

        BigDecimal bigDecimal1 = newBigDecimal(v1);
        BigDecimal bigDecimal2 = newBigDecimal(v2);
        return bigDecimal1.multiply(bigDecimal2).setScale(scale, RoundingMode.DOWN).toPlainString();
    }


    /**
     * 两个数值字符串相除
     * 除数为0时，默认返回0
     *
     * @param v1 被除数
     * @param v2 除数
     * @return 商
     */
    public static String div(String v1, String v2) {
        BigDecimal bigDecimal1 = newBigDecimal(v1);
        BigDecimal bigDecimal2 = newBigDecimal(v2);
        if (bigDecimal2.compareTo(BigDecimal.ZERO) != 0) {
            return bigDecimal1.divide(bigDecimal2, DIVIDE_DEFAULT_SCALE, RoundingMode.DOWN).toPlainString();
        } else {
            return DIVIDE_ZERO_DEFAULT_VALUE;
        }
    }

    /**
     * 两个数值字符串相除
     * 除数为0时，默认返回0
     *
     * @param v1    被除数
     * @param v2    除数
     * @param scale 精度
     * @return 商
     */
    public static String div(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }

        BigDecimal bigDecimal1 = newBigDecimal(v1);
        BigDecimal bigDecimal2 = newBigDecimal(v2);
        if (bigDecimal2.compareTo(BigDecimal.ZERO) != 0) {
            return bigDecimal1.divide(bigDecimal2, scale, RoundingMode.DOWN).toPlainString();
        } else {
            return DIVIDE_ZERO_DEFAULT_VALUE;
        }
    }

    /**
     * 比较两个数值字符串的大小
     *
     * @param v1
     * @param v2
     * @return {@code 1} if {@code v1 > v2}, {@code -1} if {@code v1 < v2},
     * {@code 0} if {@code v1 == v2}.
     */
    public static int compare(String v1, String v2) {
        BigDecimal bigDecimal1 = newBigDecimal(v1);
        BigDecimal bigDecimal2 = newBigDecimal(v2);
        return bigDecimal1.compareTo(bigDecimal2);
    }

    /**
     * 获取增长率
     * 例: getGrowthRete(100, 160); return"60"
     *
     * @param buyPrice 购入价
     * @param curPrice 现价
     * @return
     */
    public static String getGrowthRate(String buyPrice, String curPrice) {
        String growthValue = BigDecimalUtils.sub(curPrice, buyPrice);
        // 这里先乘100再除，是因为除法可能会导致精度丢失，而乘法不会
        String tempGrowthValue = BigDecimalUtils.mul(growthValue, "100");
        return BigDecimalUtils.div(tempGrowthValue, buyPrice);
    }

    /**
     * 获取增长率
     * 例: getGrowthRate(30, 130, 2); return"333.33"
     *
     * @param buyPrice 购入价
     * @param curPrice 现价
     * @param scale    精度
     * @return
     */
    public static String getGrowthRate(String buyPrice, String curPrice, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }

        String growthValue = BigDecimalUtils.sub(curPrice, buyPrice);
        // 这里先乘100再除，是因为除法可能会导致精度丢失，而乘法不会
        String tempGrowthValue = BigDecimalUtils.mul(growthValue, "100");
        return BigDecimalUtils.div(tempGrowthValue, buyPrice, scale);
    }

    /**
     * 获取数值字符串的整数部分(不包含正负号)
     * 例: getFractionPart(-100.0198, 2); return"100"
     *
     * @param value
     * @return
     */
    public static String getIntegralPart(String value) {
        BigDecimal bigDecimal = newBigDecimal(value);
        return bigDecimal.setScale(0, RoundingMode.DOWN).abs().toPlainString();
    }

    /**
     * 获取数值字符串的小数部分，并做格式化(不包含正负号)
     * 例: getFractionPart(-100.0198, 2); return".01"
     *
     * @param value
     * @param scale
     * @return
     */
    public static String getFractionPart(String value, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }

        BigDecimal bigDecimal = newBigDecimal(value);
        BigDecimal fractionPartDecimal = bigDecimal.subtract(new BigDecimal(bigDecimal.intValue()));

        String scaleResult = fractionPartDecimal
                .setScale(scale, RoundingMode.DOWN)
                .abs()
                .toPlainString();
        return scaleResult.replaceFirst("0", "");
    }

    /**
     * 设置数值字符串的精度(位数不足时末尾补0,位数太长时直接截取不做四舍五入)
     *
     * @param value
     * @param scale
     * @return
     */
    public static String setScaleTrailingZero(String value, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }

        BigDecimal bigDecimal = newBigDecimal(value);
        return bigDecimal.setScale(scale, RoundingMode.DOWN).toPlainString();
    }

    /**
     * 移除数值字符串小数点后无意义的“0”
     *
     * @param value
     * @return
     */
    public static String trailingZero(String value) {
        BigDecimal bigDecimal = newBigDecimal(value);
        return bigDecimal.stripTrailingZeros().toPlainString();
    }

    /**
     * 设置数值字符串的精度(位数不足时末尾不补0,位数太长时直接截取不做四舍五入)
     *
     * @param value
     * @param scale
     * @return
     */
    public static String setScaleStripTrailingZero(String value, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }

        BigDecimal bigDecimal = newBigDecimal(value);
        return bigDecimal.setScale(scale, RoundingMode.DOWN).stripTrailingZeros().toPlainString();
    }

    /**
     * 判断一个数能否被整除
     *
     * @param v1 被除数
     * @param v2 除数
     * @return
     */
    public static boolean divideExactly(String v1, String v2) {
        BigDecimal bigDecimal1 = newBigDecimal(v1);
        BigDecimal bigDecimal2 = newBigDecimal(v2);
        if (bigDecimal2.compareTo(BigDecimal.ZERO) != 0) {
            BigDecimal remainder = bigDecimal1.divideAndRemainder(bigDecimal2)[1];
            return remainder.compareTo(BigDecimal.ZERO) == 0;
        } else {
            return true;
        }
    }

}
